﻿#r @"..\FSChart\bin\Release\FSChart.dll"

open FSChart
open FSChart.Builder

#r @"System.Windows.Forms.DataVisualization.dll"

open System.IO
open System.Drawing
open System.Windows.Forms
open System.Windows.Forms.DataVisualization.Charting

Directory.SetCurrentDirectory(__SOURCE_DIRECTORY__)

// ------------------------------------
// FSChart ?
// ------------------------------------
// 
// FSChart is basically a functional wrapper over the Microsoft Chart Control.
// It allows to benefit from (a strict subset of) its rich feature set while
// avoiding the unpleasant experience of manipulating a gigantic nested mutable
// object from F#.

// This version is a refactoring of a previous project hosted on the defunct code
// gallery (http://archive.msdn.microsoft.com/fschart), aiming for increased ease
// of use in the interactive scenario as well as a more functional feel.

// Charts are defined as hierarchical functional values which specify the layout,
// the formats and the data series. Chart values can be created as a single
// expression or composed from more primitive components. Syntax sugar is provided
// in the form of functions in a Builder module that allow to create complex
// structures in a fluent manner, with minimum noise.

// Charts are rendered through the MSChart module, either in a fresh form, or in
// an existing Chart Control :
// 
// module MSChart =
//     val displayIn : Chart -> ChartDef -> unit
//     val display   : bool -> int -> int -> ChartDef -> Form * Chart

// 
// That being said, feel free to skip to the samples, experiment, and then come back
// if you need some clarification !
// 

// ------------------------------------
// Chart definitions
// ------------------------------------
// 
// A chart definition is essentially a collection of plotting areas containing series
// and arranged in a layout that is a recursive subdivision of the rendering area.
// 
// type ChartDef =
//     { Title      : TitleDef   option
//       Border     : LineDef    option
//       Background : Background option
//       Contents   : ChartContents }
// 
// type ChartContents =
//     | Plot   of PlotDef
//     | StackV of float list * ChartContents list
//     | StackH of float list * ChartContents list
//     | Margin of float * float * float * float * ChartContents
// 
// type PlotDef = AreaDef * (SeriesDef * SeriesData) list
// 
// Area definitions specify formats and attributes for the title, the legend and
// the axes. Their precise representation as a record type is not important as
// they will be created and transformed using builder functions (exemples give
// a good intuition of how to use these). Furthermore, a Gallery static class
// defines an Area method with optional named arguments which allows to create
// areas with predefined default attributes that are sufficient for most uses.
// 
// type Gallery =
//    static member Area (?Title, ?Face, ?TextSz, ?Legend, ?TypeX, ?TitleX, ?LabelsX, ?GridX, ?TitleY, ?LabelsY, ?GridY) = ...
// 
// SeriesDef is a record type which specifies a series graphical attributes.
// Again, the representation is not important as they will usually be created
// with builder functions like in : scatter |> marker (circle >> color (alpha 32 blue))
// which defines a scatter series with semi-transparent blue circle markers.
// 
// SeriesData specifies the series data and optional labels. The record type
// is augmented with overloaded members which cover most common cases.
// 
// type DataX =
//    | XD of DateTime[]
//    | XF of float   []
//    | XS of string  []
// 
// type SeriesData =
//    { DataX  : DataX
//      DataY  : float [] list
//      Labels : string[] option }
//    
//    static member DY(d, y) =
//        { DataX = XD d; DataY = [y]; Labels = None   }
//    
//    static member DY(d, ys) =
//        { DataX = XD d; DataY = ys ; Labels = None   }
//    
//    static member XY(x, y) =
//        { DataX = XF x; DataY = [y]; Labels = None   }
//    
//    static member XY(x, ys) =
//        { DataX = XF x; DataY = ys ; Labels = None   }
//    
//    static member XYL(x, y, l) =
//        { DataX = XF x; DataY = [y]; Labels = Some l }
//    
//    static member SY(s, y) =
//        { DataX = XS s; DataY = [y]; Labels = None   }
//    
//    static member SY(s, ys) =
//        { DataX = XS s; DataY = ys ; Labels = None   }
//    
//    static member XF(x, f) =
//        { DataX = XF x; DataY = [ x |> Array.map f ]; Labels = None }
// 
//    static member XF(x, fs) =
//        { DataX = XF x; DataY = [ for f in fs -> Array.map f x ]; Labels = None }
// 

// ------------------------------------
// Builder
// ------------------------------------
// 
// In order to avoid creating and transforming chart values components directly,
// i.e. with nested record expressions and DU constructors, syntax sugar is provided
// in the form of default values, as well as constructor and transform functions
// in the Builder and Builder.C modules.
// 
// --- Defaults and Constructors ---
// 
// The C module contains functions dealing with the ChartContents DU.
// 
//  C.create : ChartContents -> ChartDef
//    creates a chart value with the given contents.
// 
// Each constructor of the discriminated union is associated with a
// curried function having the same name i.e. C.plot, C.stackV,
// C.stackH and C.margin. The first argument of the stack functions
// is a list of relative sizes for the stack's components (they don't
// need to sum to one). C.margin's first arguments are >= 0.0 && <= 1.0
// margins for the left, top, right and bottom sides of the enclosing box
// respectively. Additional functions implementing common uses of the
// previous ones are also provided (see the samples).
// 
// The helper functions C.single and C.lattice allow to easily create charts with
// a single area or areas arranged in a regular grid respectively.
// 
// AreaDef   :
//  - areaD (default for an area with X Data of type DateTime)
//  - areaF (default for an area with X Data of type float   )
//  - areaS (default for an area with X Data of type string  )
// 
// AxisDef   :
//  - axis  (default for a basic axis with ticks on)
// 
// SeriesDef :
// A default value or constructor is provided for each series type in :
//   line, fastLine, stepLine, scatter, fastPoint, ohlc, filledArea, stackedArea,
//   band, pie, polar, radar, radarArea, bar, barH, stackedBar, stackedBarH,
//   candles, spline, splineArea, splineBand.
// - the constructor argument is a relative width (0 < w <= 1.0) for bar    like series.
// - the constructor argument is a tension        (0 < t <= 2.0) for spline like series.
// 
// --- Transforms ---
// 
// Each "unique" field is associated with a function having the same name (camel-
// cased), which acts as a transform over the enclosing record accepted as the last
// argument.
// 
// If the field is a simple attribute, then the function first argument(s) determine
// the value to be set, e.g. : color blue series <=> { series with Color = blue }
// 
// If the field is itself a record with type R, then the function first argument is 
// a function (R -> R), e.g. : labels f axis <=> { axis with Labels = f axis.Labels }
// 
// The actual functions definitions are slightly different for option fields (they always
// create a Some value and the second variety must substitute a default value when the
// original field is a None).
// 
// A nested record can then be transformed concisely via pipelining and composition :
// 
// let area' = area
//             |> title ( text "Volatility" >> color darkBlue )
//             |> axisX ( labels ( color blue >> format "F2" ) >> grid ( dash gray 1 ) )
// 
// Note that color seems to have type Color -> TitleDef -> TitleDef in one place and
// Color -> LabelDef -> LabelDef in another without possibly being a generic function.
// 
// Readers with some F# experience will have recognized a function with statically
// resolved type parameters : color and other functions associated with fields occurring
// at different places in the record hierarchy must be defined inline with an ad-hoc
// member constraint (which reminds me to mention that all records are indeed augmented
// with setter members for every field).
// 
// let inline color c (e : ^e) = (^e : (member SetColor : Color -> ^e) (e, c))
// 
// Additional sugaring is provided for discriminated unions and fields assuming a finite
// number of values, e.g. : top can be used instead of location Top, circle instead of
// shape Circle, loglin 1.0 instead of scale ( LogLin 1.0 ) ...
// 
// --- Remark ---
// 
// In the context of an application, it is recommended to open the Builder module in
// a limited scope to avoid namespace pollution (explicit qualification is probably not
// an option as it would ruin the library's "syntactic nicety").
// 

// ------------------------------------------------------------------------------------------------
// Data generating functions
// ------------------------------------------------------------------------------------------------

let uniform =
    let rnd = new System.Random()
    fun () -> rnd.NextDouble()

let gauss() =
    let rec sumU n acc = if n <= 0 then acc else sumU (n - 1) (acc + uniform())
    sumU 12 0.0 - 6.0

let brownian =
    Seq.unfold (fun b -> Some (b, b + gauss())) 0.0

let brownianPath n =
    brownian |> Seq.take (n + 1) |> Seq.map (fun x -> x / sqrt (float n)) |> Seq.toArray

let brownianX n = Array.init (n + 1) (fun k -> float k / float n)
let brownianY n = brownianPath n

let aggregate period ps =
    let n = Array.length ps
    let p = n / period
    let o = Array.zeroCreate p
    let h = Array.zeroCreate p
    let l = Array.zeroCreate p
    let c = Array.zeroCreate p
    let rec bar k =
        if k >= p then () else
        let i1 = k * period
        let i2 = i1 + period - 1
        o.[k] <- ps.[i1]
        c.[k] <- ps.[i2]
        h.[k] <- Array.max ps.[i1 .. i2]
        l.[k] <- Array.min ps.[i1 .. i2]
        bar (k + 1)
    bar 0
    o, h, l, c

let brownianBars n p =
    let y = brownianY n
    let o, h, l, c = aggregate p (Array.map ((+)10.0) y)
    let d = Array.init (n / p) (fun t -> DateTime.Today.AddDays(float <| t - n / p))
    SeriesData.DY ( d, [o; h; l; c] )

let histogram x0 x1 n xs =
    let h = Array.zeroCreate n
    let dx = (x1 - x0) / float n
    let nx = float <| Array.length xs
    xs
    |> Array.iter (fun x ->
        if x > x0 && x <= x1
        then let k = int <| ceil ((x - x0) / dx) - 1.0 in h.[k] <- h.[k] + 1.0)
    Array.map (fun c -> c / (dx * nx)) h

let scatterhist rho k x0 x1 n =
    let xs = let dx = (x1 - x0) / float n in Array.init n (fun i -> x0 + (float i + 0.5) * dx)
    let y1s, y2s = Array.init k (fun _ -> gauss()), Array.init k (fun _ -> gauss())
    Array.iteri2 (fun i y1 y2 -> y2s.[i] <- rho * y1 + sqrt(1.0 - rho * rho) * y2) y1s y2s
    y1s, y2s, xs, histogram x0 x1 n y1s, histogram x0 x1 n y2s

let sp, spma, spchan, spatr =
    
    let date (s : string) =
        let ymd =  int s
        let d   =  ymd % 100
        let m   = (ymd % 10000) / 100
        let y   =  ymd / 10000
        DateTime(y, m, d)
    
    let lines = File.ReadAllLines "Data-SP.txt"
    let a() = Array.zeroCreate lines.Length
    let d, o, h, l, c, v = a(), a(), a(), a(), a(), a()
    lines
    |> Seq.iteri (fun i s ->
        let fields = s.Split [|','|]
        d.[i] <- date  fields.[0]
        o.[i] <- float fields.[1]
        h.[i] <- float fields.[2]
        l.[i] <- float fields.[3]
        c.[i] <- float fields.[4]
        v.[i] <- float fields.[6]
    )
    
    let tr = Array.init d.Length (fun i -> if i = 0 then h.[0] - l.[0] else max h.[i] c.[i-1] - min l.[i] c.[i-1])
    
    let findIndex t = Array.findIndex (fun s -> s >= t) d
    
    let foldRange f i1 i2 acc (a : array<_>) =
        let mutable acc = acc
        for i = i1 to i2 do
            acc <- f acc a.[i]
        acc
    
    fun t1 t2 ->
        let i1, i2 = findIndex t1, findIndex t2
        d.[i1..i2], o.[i1..i2], h.[i1..i2], l.[i1..i2], c.[i1..i2], v.[i1..i2]
    ,
    fun n t1 t2 ->
        let i1, i2 = findIndex t1, findIndex t2
        if n < 1 || n > i1 + 1 then failwith "not enough data"
        Array.init (i2 - i1 + 1) (fun k -> (foldRange (+) (i1 + k - n + 1) (i1 + k) 0.0 c) / float n)
    ,
    fun n t1 t2 ->
        let i1, i2 = findIndex t1, findIndex t2
        if n < 1 || n > i1 + 1 then failwith "not enough data"
        Array.init (i2 - i1 + 1) (fun k -> foldRange min (i1 + k - n + 1) (i1 + k) 1.0e9 l),
        Array.init (i2 - i1 + 1) (fun k -> foldRange max (i1 + k - n + 1) (i1 + k) 0.0   h)
    ,
    fun n t1 t2 ->
        let i1, i2 = findIndex t1, findIndex t2
        if n < 1 || n > i1 + 1 then failwith "not enough data"
        Array.init (i2 - i1 + 1) (fun k -> (foldRange (+) (i1 + k - n + 1) (i1 + k) 0.0 tr) / float n)

// ------------------------------------------------------------------------------------------------
// The Chart Samples (at last !)
// ------------------------------------------------------------------------------------------------

open Builder

// Use Ctrl-C in an MSChart Window to copy the chart as a png image to the clipboard.
// Use Ctrl-Shift-C to copy the chart as an enhanced metafile image to the clipboard.
// Use Ctrl-S to save the chart image into a file. Different file formats are supported.
// The same functions are also available through a context menu.

// ------------------------------------
// Sample 01 - simple
// ------------------------------------
// 
// The single function creates a single area chart definition from an area definition
// and a list of pairs series definition * series data.
// 
// The static method Gallery.Area allows to create simple area definitions suitable for
// quick interactive visualizations by setting a few optional arguments (intellisense
// will happily show those).
// 

let simple() =
    let chart =
        C.single
            ( Gallery.Area(Legend=true, Title="CHART 101", TitleX="Time (s)", TitleY="Signal (mV)") )
            [ line |> text "sensor 1", SeriesData.XF ([|0.0..0.01..6.28|], sin)
              line |> text "sensor 2", SeriesData.XF ([|0.0..0.01..6.28|], (fun x -> sin(2.0*x))) ]
    
    MSChart.display true 800 600 chart |> ignore

// ------------------------------------
// Sample 02 - formats
// ------------------------------------
// 
// Shows how to apply formats to charts, areas, titles, legends, axes and series.
// 
// - Note how we define common formats for the axes as functions ax and ax2 and then
// customize each axis by composing those with additional transforms.
// 
// - linear and linearI are scale specifiers for float axes with an argument that
// determines the labels density (1.0 is the base case, 2.0 will give half as many,
// 0.5 twice as many, ...). linear adjusts an axis extrema so as they coincide with
// a label while linearI doesn't and will thus usually be preferred for the X axis.
// 
// - arialR is just a font setter helper defined in the builder module as :
//   let inline arialR sz = font ("Arial", float32 sz, FontStyle.Regular).
// 

let formats() =
    
    let xs = [| -15.0..0.25..15.0 |]
    let sinc x = if abs x <= 1.0e-6 then 1.0 else sin x / x
    let sincm, sincp = (fun x -> sinc x - 0.1), (fun x -> sinc x + 0.1)
    
    let area =
        let g   = grid ( solid lightSteelBlue 1 )
        let ax  = labels ( arialR 8 >> color blue ) >> solid blue 1
        let ax2 = labels ( arialR 8 >> color seaGreen ) >> solid seaGreen 1 >> linear 1.0
        areaF |> axisX ( ax >> linearI 1.0 >> g ) |> axisY ( ax >> linear 1.0 ) |> axisY2 ( ax2 >> g )
              |> border steelBlue 1 |> backColor aliceBlue |> legend ( inside >> transparent >> arialR 8 )
    
    let chart =
        C.single
            area
            [ line |> text "id"   |> dash  blue 1,
              SeriesData.XF( xs, id)
              line |> text "sin"  |> solid blue 2,
              SeriesData.XF( xs, sin)
              line |> text "sinc" |> color seaGreen |> onAxisY2,
              SeriesData.XF( xs, sinc)
              band |> text "band" |> color (alpha 128 seaGreen) |> onAxisY2 ,
              SeriesData.XF( xs, [sincm; sincp]) ]
        
        |> gradient white lightSteelBlue
        |> title ( text "FORMATS" >> color steelBlue >> arialR 14 )
    
    MSChart.display true 800 600 chart |> ignore

// ------------------------------------
// Sample 03 - signals
// ------------------------------------
// 
// Shows how to render a new chart in an existing control.
// Also, an F# demo without some async wouldn't be no fun.
// 

let signals() =
    
    let xs = [| -3.15 .. 0.025 .. 3.15 |]
    let psi   k x = exp(-abs x)*sin(2.0*(x - float k / 20.0))
    let gamma k x = cos (x + float k / 10.0)
    
    let series1 = fastLine  |> text "Psi( t )"
    let series2 = fastPoint |> text "Gamma( t )" |> color lightSeaGreen |> marker ( circle >> size 2 )
    
    let area =
        let ax = labels ( arialR 8 >> format "F1" ) >> grid ( solid lightSteelBlue 1 )
        areaF |> axisX ( ax >> linearI 2.0 ) |> axisY ( ax >> linear 2.0 ) |> backColor aliceBlue
              |> legend ( inside >> transparent >> arialR 8 )
              |> title  ( text "SIGNALS" >> arialR 14 >> color steelBlue )
    
    let chart k =
        C.single
            area
            [ series1, SeriesData.XF(xs, psi k)
              series2, SeriesData.XF(xs, gamma k) ]
        |> gradient white lightSteelBlue
    
    let form, mschart = chart 0 |> MSChart.display true 800 600
    
    let rec draw k =
        async {
            do MSChart.displayIn mschart (chart k)
            do! Async.Sleep(15)
            do! draw (k + 1) }
    
    let cancellationSource = new System.Threading.CancellationTokenSource()
    form.Closing.Add(fun _ -> cancellationSource.Cancel())
    Async.StartImmediate(draw 0,cancellationSource.Token)

// ------------------------------------
// Sample 04 - stockchart
// ------------------------------------

// The lattice function creates a chart definition where areas are arranged in a regular lattice.
// Its first two arguments are an outer margin and an inner margin of the form (left, top, right, bottom),
// and the last one defines the contents as a list of rows, each of which is a list of pairs of an area
// definition and a list of pairs series definition * series data (this is not as complex as it sounds
// since the code structure will actually map the chart's visual structure : see below). 
// 
// Margins are required in multiple area charts whenever area titles or legends are positioned outside
// (which is the default), or they would otherwise overlap with adjacent areas.
// Margin components are expressed as proportions ( 0.0 <= p <= 1.0 ) of the enclosing rectangle lengths.
// 
// Since we don't provide legend texts at series definition point, they are resolved as the series names.
// Area names are generated in the form A(area number).
// Series names are generated in the form A(area number)S(series number).
// These names allow to retrieve references to the objects in the Chart control's hierarchy if you
// need to perform some post-processing.

let stockchart() =
    let data() =
        brownianBars 1000 20

    let area  =
        areaD
        |> axisX ( labels ( arialR 7 >> autofit on >> format "yyyy\nMM-dd" ) >> margin on )
        |> axisY ( labels ( arialR 7 >> format "F1" ) >> linear 2.0 )
        |> legend ( transparent >> arialR 8 >> top >> near )
        |> gradient white lightSteelBlue
    
    let chart =
        C.lattice
            (0.0, 0.1, 0.03, 0.0) (0.0, 0.1, 0.0, 0.0)
            [ [ area, [candles 0.5, data()]; area, [ohlc       , data()] ]
              [ area, [ohlc       , data()]; area, [candles 0.5, data()] ] ]
        |> title ( text "Stock Prices" >> arialR 16 ) |> backColor lightSteelBlue
    
    MSChart.display true 800 600 chart |> ignore

// ------------------------------------
// Sample 05 - gauss2h
// ------------------------------------
// 
// argument : -1.0 <= rho <= 1.0 (correlation coefficient)
// 

let gauss2h rho =
    let k, x0, x1, n, sx = 10000, -4.0, 4.0, 32, 1.0
    let y1s, y2s, xs, h1, h2 = scatterhist rho k x0 x1 n
    
    let axVal  = axis  |> labels ( arialR 7 ) |> range ( x0, x1, sx )
    let axVal0 = axVal |> crossAt 0.0 false
    let axPdf  = axVal |> range (0.0, 0.5, 0.1)
    let area1  = areaF |> axisX ( setAs axVal  ) |> axisY ( setAs axPdf  )
    let area2  = areaF |> axisX ( setAs axVal0 ) |> axisY ( setAs axVal0 )
    let areaR  = areaF |> title ( arialR 14 >> color blue >> text (sprintf " \nRHO\n=\n%.2f" rho) >> inside )
                       |> axisX ( solid blue 1 ) |> axisY ( solid blue 1 ) |> border blue 1
    
    let chart  =
        C.create <|
        C.stackV [ 0.3; 0.7 ]
            [ C.stackH [ 0.7; 0.3 ]
                [ C.plot area1
                    [ bar 1.0 |> color blue |> border darkBlue 1,
                      SeriesData.XY (xs, h1) ] 
                  C.marginU 0.1 ( C.plot areaR [] ) ]
              C.stackH [ 0.7; 0.3 ]
                [ C.plot ( area2 |> alignV "A1" )
                    [ scatter |> marker (circle >> color (alpha 32 blue)),
                      SeriesData.XY (y1s, y2s) ]
                  C.plot area1
                    [ barH 1.0 |> color blue |> border darkBlue 1,
                      SeriesData.XY (xs, h2) ] ] ]
    
    MSChart.display true 600 600 chart |> ignore

// ------------------------------------
// Sample 06 - futures
// ------------------------------------
// 
// timeX is a scale specifier useful when dealing with regular financial data :
// Whereas the chart control treats time as a continuous variable by default, and would
// show "holes" for daily series with week-ends/holidays or intraday series with overnight
// periods, timeX instructs it to treat time series as indexed sequences with "meaningless"
// date labels and show equally spaced successive values (which requires synchronization
// when different series are displayed in the same area). Its argument indicates a desired
// number of subdivisions, which the underlying algorithm will try to satisfy at best while
// positioning the labels at natural boundaries (start of day, week, month, ...).
// 
// Note the use of alignV to get a precise alignment of the two plotting areas (margin was
// also required in this case to obtain alignment at the data point level).

let futures() =
    
    let t1, t2 = DateTime(1984,01,15), DateTime(1984,08,20)
    let ds, op, hi, lo, cl, vo = sp t1 t2
    let ma1, ma2 = spma 40 t1 t2, spma 10 t1 t2
    let ch1, ch2 = spchan 20 t1 t2
    let atr      = spatr  10 t1 t2
    
    let g     = grid ( dash gainsboro 1 )
    let area0 = areaD |> backColor aliceBlue |> legend ( arialR 7 >> top >> center )
                      |> axisX  ( margin on >> timeX 8 >> g ) |> axisY ( labels ( arialR 7 ) )
    let area1 = area0 |> axisX  ( ticks off ) |> axisY ( g >> linear 1.0 )
    let area2 = area0 |> axisX  ( labels ( arialR 7 >> format "yyyy\nMM-dd" >> autofit on ) )
                      |> axisY  ( linear 4.0 >> labels ( format "0,K" ) )
                      |> axisY2 ( linear 2.0 >> labels ( arialR 7 ) )
    
    let chart =
        C.create <|
        C.splitV 0.7
            ( C.marginT 0.1
                ( C.plot area1
                    [ candles 0.5 |> color seaGreen |> text "SP",
                      SeriesData.DY (ds, [ op; hi; lo; cl ])
                      band |> color (alpha 32 lightSkyBlue) |> border (alpha 96 skyBlue) 1 |> text "CHAN20",
                      SeriesData.DY (ds, [ch1; ch2])
                      line |> text "MA40",
                      SeriesData.DY (ds, ma1)
                      line |> text "MA10",
                      SeriesData.DY (ds, ma2) ] ) )
            ( C.marginT 0.15
                ( C.plot ( area2 |> alignV "A1" )
                    [ bar 1.0 |> color skyBlue |> border blue 1 |> text "VOLUME",
                      SeriesData.DY (ds, vo)
                      line |> color darkRed |> onAxisY2 |> text "ATR10",
                      SeriesData.DY (ds, atr) ] ) )
    
    MSChart.display true 800 600 chart |> ignore

// ------------------------------------
// Sample 07 - scales
// ------------------------------------
// 
// A graphical illustration of the available scale settings for float axes.
// 
// This sample also shows why it is convenient to use immutable values to represent
// chart components. An area template with common attributes is produced and then
// specialized for each subplot by setting the title and the scale for axis Y without
// altering the original.

let scales() =
    
    let dataX  = brownianX 250
    let series = [ for k in 1 .. 10 -> line, SeriesData.XY( dataX, Array.map exp (brownianY 250)) ]
    
    let scales = [ [ linear 1.0, "linear 1.0"; linear  2.0, "linear 2.0" ; linearI 2.0, "linearI 2.0" ]
                   [ loglin 2.0, "loglin 2.0"; loglog 10.0, "loglog 10.0"; loglog  2.0, "loglog 2.0"  ] ]
    
    let area = Gallery.Area() |> axisY ( labels ( format "F2" ) )
    
    let chart =
        C.lattice
            (0.0, 0.1, 0.05, 0.0)
            (0.0, 0.1, 0.0 , 0.0)
            [ for row in scales ->
                [ for (s, t) in row -> area |> axisY s |> title ( text t ), series  ] ]
        |> title ( arialR 16 >> text "Axis Scale Specifiers" )
    
    MSChart.display true 800 600 chart |> ignore

// ------------------------------------
// Sample 08 - corporate
// ------------------------------------
// 
// Shows how to use categorical data and to set series labels, as well as fancy chart
// styles and formats but is otherwise nonsensical. Just note that we have to declare
// the transform blueBox inline so that it can be applied to different chart elements.
// 

let corporate() =
    
    let companies = [| "Foogle"; "Bang"; "yaZoo" |]
    let factors   = [| "Factor 1"; "Factor 2"; "Factor 3" |]
    
    let ax8 = labels ( arialR 8 )
    let blueGrid = grid ( solid steelBlue 1 )
    let inline blueBox e = e |> backColor aliceBlue |> border steelBlue 1
    let areaSY = areaS |> axisX ( ax8 >> margin off ) |> axisY ( ax8 >> linear 2.0 )
                       |> transparent |> legend ( bottom >> center >> arialR 8 >> blueBox )
    
    let plot11 =
        let ax = ax8 >> linear 2.0 >> blueGrid
        C.plot
            ( areaF |> axisX ( ax ) |> axisY ( ax ) |>  blueBox )
            [ scatter |> marker ( star >> size 12 ) |> labels ( arialR 10 >> color seaGreen ),
              SeriesData.XYL ([| 0.25; 0.75; -0.50 |], [| -0.75; 0.50; -0.25 |], companies)
              line |> dash skyBlue 1,
              SeriesData.XF  ([| -1.0..0.1..1.0 |], (fun x -> cos x * sin x)) ]
    
    let plot12 =
        C.plot
            ( areaS |> style3D ( view (20, 30, 30) ) |> transparent )
            [ pie |> labels ( arialR 10 >> color steelBlue >> blueBox ),
              SeriesData.SY (companies, [| 0.40; 0.25; 0.35 |]) ]
    
    let plot21 =
        C.marginB 0.2 <|
        C.plot
            ( areaSY |> alignV "A1" )
            [ stackedArea |> text "Legal" |> color lightSeaGreen,
              SeriesData.SY (companies, [| 0.80; 0.05; 0.15 |])
              stackedArea |> text "Research" |> color steelBlue,
              SeriesData.SY (companies, [| 0.15; 0.65; 0.10 |])
              stackedArea |> text "Intelligence" |> color gold,
              SeriesData.SY (companies, [| 0.05; 0.30; 0.75 |]) ]
    
    let plot22 =
        C.marginB 0.2 <|
        C.plot
            ( areaSY |> axisY ( blueGrid ) )
            [ radar |> text "Foogle" |> marker ( star >> size 6 ),
              SeriesData.SY (factors, [| 0.80; 0.15; 0.75 |])
              radar |> text "Bang"   |> marker ( diamond >> size 6 ),
              SeriesData.SY (factors, [| 0.25; 0.65; 0.30 |])
              radar |> text "yaZoo"  |> marker ( triangle >> size 6 ),
              SeriesData.SY (factors, [| 0.05; 0.30; 0.75 |]) ]
    
    let chart =
        C.create <|
        C.marginT 0.20
            ( C.splitV 0.4
                 ( C.splitH 0.5 plot11 plot12 )
                 ( C.splitH 0.5 plot21 plot22 ) )
        |> title ( text "\nFoogle, Bang & yaZoo 3-Way Merger : Corporate Facts\n\n"
                   >> arialR 12 >> color steelBlue >> border steelBlue 1 >> gradient white lightSteelBlue )
        |> gradient white lightSteelBlue
    
    MSChart.display true 600 600 chart |> ignore

// ------------------------------------
// Sample 09 - save
// ------------------------------------
// 
// MSChart.save is used to save a chart to a png file without creating a form first.
// Different file formats are supported. They are enumerated in 
// System.Windows.Forms.DataVisualization.Charting.ChartImageFormat.
// 

let save() =
    let chart =
        C.single
            ( Gallery.Area(Legend=true, Title="CHART 101", TitleX="Time (s)", TitleY="Signal (mV)") )
            [ line |> text "sensor 1", SeriesData.XF ([|0.0..0.01..6.28|], sin)
              line |> text "sensor 2", SeriesData.XF ([|0.0..0.01..6.28|], (fun x -> sin(2.0*x))) ]
    
    let filename =
        let desktop = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Desktop)
        System.IO.Path.Combine(desktop, "FSChart Example.png")
    
    MSChart.save ChartImageFormat.Png 800 600 filename chart |> ignore

